﻿using System;
using System.Management.Automation;
using System.Management.Automation.Runspaces;
using System.Windows.Forms;
using Excel = Microsoft.Office.Interop.Excel;
using Microsoft.Office.Interop.Excel;
using System.IO;

namespace ExcelPoSH
{
    public partial class Console : UserControl
    {
        private Excel.Application app;
        private Runspace runspace;
        private Pipeline pipeline;

        // for pipeline / txtOutput communication

        private delegate void SetTextDelegate(string text, bool append);
        
        public Console(Excel.Application app)
        {
            InitializeComponent();

            // Create the runspace for running the scripts
            // and make the current Microsoft,Office.Interop.Excel.Application
            // instance available to PowerShell as a variable named $Application

            this.app = app;
            runspace = RunspaceFactory.CreateRunspace(new ExcelPSHost(app));
            runspace.Open();

            // Add Excel VBA globals as automatic variable to the session

            runspace.SessionStateProxy.SetVariable("Application", app);
            runspace.SessionStateProxy.PSVariable.Set(new ActiveCell("ActiveCell"));
            runspace.SessionStateProxy.PSVariable.Set(new ActiveChart("ActiveChart"));
            runspace.SessionStateProxy.PSVariable.Set(new ActivePrinter("ActivePrinter"));
            runspace.SessionStateProxy.PSVariable.Set(new ActiveSheet("ActiveSheet"));
            runspace.SessionStateProxy.PSVariable.Set(new ActiveWindow("ActiveWindow"));
            runspace.SessionStateProxy.PSVariable.Set(new ActiveWorkbook("ActiveWorkbook"));
            // runspace.SessionStateProxy.PSVariable.Set(new Cells("Cells"));

            runspace.RunspaceConfiguration.Cmdlets.Append(
                new CmdletConfigurationEntry("Read-ExcelRange",
                    typeof(ReadExcelRangeCommand), null));
            runspace.RunspaceConfiguration.Cmdlets.Append(
                new CmdletConfigurationEntry("Write-Excel",
                    typeof(WriteExcelCommand), null));
            runspace.RunspaceConfiguration.Cmdlets.Update();

            txtInput.Tag = new ScriptFileProps(false, null);
        }

        // Run script - Stop Script - Clear output toolstrip buttons

        private void runToolstripButton_Click(object sender, EventArgs e)
        {
            if (txtInput.Text.Length > 0)
            {
                txtOutput.Clear();
                RunScript(txtInput.Text);
            }
        }

        private void stopToolStripButton_Click(object sender, EventArgs e)
        {
            if (pipeline != null)
            {
                pipeline.StopAsync();
            }
        }

        private void clearToolStripButton_Click(object sender, EventArgs e)
        {
            if (pipeline != null)
            {
                if (pipeline.PipelineStateInfo.State == PipelineState.Completed |
                    pipeline.PipelineStateInfo.State == PipelineState.Failed |
                    pipeline.PipelineStateInfo.State == PipelineState.Stopped)
                {
                    SetText("", false);
                }
            }
        }

        // New - Open - Save toolstrip buttons

        private void newToolStripButton_Click(object sender, EventArgs e)
        {
            if ((txtInput.Tag as ScriptFileProps).Dirty)
            {
                SaveScriptFile();
            }

            txtInput.Clear();
            txtInput.Tag = new ScriptFileProps(false, null);
        }

        private void openToolStripButton_Click(object sender, EventArgs e)
        {

            if ((txtInput.Tag as ScriptFileProps).Dirty)
            {
                if (MessageBox.Show(
                    "Do you want to save the current script first?",
                    "Save script", MessageBoxButtons.YesNo)
                    == DialogResult.Yes)
                {
                    SaveScriptFile();
                    OpenScriptFile();
                }
            }
            else
            {
                OpenScriptFile();
            }
        }

        private void saveToolStripButton_Click(object sender, EventArgs e)
        {
            SaveScriptFile();
        }

        // Cut - Copy - Paste toolstrip buttons

        private void cutToolStripButton_Click(object sender, EventArgs e)
        {
            if (txtInput.Focused)
            {
                txtInput.Cut();
            }
            else if (txtOutput.Focused)
            {
                txtOutput.Cut();
            }
        }

        private void copyToolStripButton_Click(object sender, EventArgs e)
        {
            if (txtInput.Focused)
            {
                txtInput.Copy();
            }
            else if (txtOutput.Focused)
            {
                txtOutput.Copy();
            }
        }

        private void pasteToolStripButton_Click(object sender, EventArgs e)
        {
            if (txtInput.Focused)
            {
                txtInput.Paste();
            }
            else if (txtOutput.Focused)
            {
                txtOutput.Paste();
            }
        }

        // Event handlers for txtInput - txtOutput

        // Scroll to the end of txtOutput when output is added

        private void txtOutput_TextChanged(object sender, EventArgs e)
        {
            txtOutput.ScrollToCaret();
        }

        // Mark script for saving when txtInput text is changed

        private void txtInput_TextChanged(object sender, EventArgs e)
        {
            (txtInput.Tag as ScriptFileProps).Dirty = true;
        }

        // If Tab is presses, insert form spaces at cursor position

        private void txtInput_KeyDown(object sender, KeyEventArgs e)
        {
            if (e.KeyCode == Keys.Tab)
            {
                e.SuppressKeyPress = true;
                txtInput.SelectedText = "    ";
            }
        }

        // Pipeline event handlers

        // Write data from pipeline.Error to txtOutput
        // pipeline.Error.DataReady only captures 
        // 'nonterminating errors'

        void Error_DataReady(object sender, EventArgs e)
        {
            PipelineReader<object> error =
                sender as PipelineReader<object>;

            if (error != null)
            {
                while (error.Count > 0)
                {
                    SetText(error.Read().ToString(), true);
                }
            }
        }

        // Write data from pipeline.Output to txtOutput

        void Output_DataReady(object sender, EventArgs e)
        {
            PipelineReader<PSObject> output =
                sender as PipelineReader<PSObject>;

            if (output != null)
            {
                while (output.Count > 0)
                {
                    foreach (PSObject pso in output.NonBlockingRead(10))
                    {
                        SetText(pso.ToString(), true);
                    }
                }
                return;
            }
        }

        void pipeline_StateChanged(object sender, PipelineStateEventArgs e)
        {
            Pipeline pipeline = sender as Pipeline;

            // This is needed for writing 'terminating errors'
            // messages to txtOutput. 

            if (pipeline.PipelineStateInfo.State == PipelineState.Failed)
            {
                SetText(pipeline.PipelineStateInfo.Reason.Message, true);
            }

            if (pipeline.PipelineStateInfo.State == PipelineState.Completed)
            {
                Globals.ThisAddIn.ConsoleSetFocus();
            }
        }

        // Private functions

        // Attempt to execute the contents of txtInput

        private void RunScript( string script)
        {
            pipeline = runspace.CreatePipeline();
            pipeline.Output.DataReady += new EventHandler(Output_DataReady);
            pipeline.Error.DataReady += new EventHandler(Error_DataReady);
            pipeline.StateChanged += 
                new EventHandler<PipelineStateEventArgs>(pipeline_StateChanged);

            if (pipeline.PipelineStateInfo.State == PipelineState.Stopped |
                pipeline.PipelineStateInfo.State == PipelineState.NotStarted |
                pipeline.PipelineStateInfo.State == PipelineState.Completed)
            {
                pipeline.Commands.Clear();
                // change thread culture to en-US
                pipeline.Commands.AddScript(
                    "[Threading.Thread]::CurrentThread.CurrentCulture = 'en-US'" +
                    Environment.NewLine + script);
                pipeline.Commands.Add("Out-String");
                pipeline.Commands[pipeline.Commands.Count - 1].Parameters.Add("stream");

                pipeline.InvokeAsync();
                pipeline.Input.Close();
            }
        }

        // Saves PowerShell code if txtInput.Text changed

        private void SaveScriptFile()
        {
            ScriptFileProps props = txtInput.Tag as ScriptFileProps;

            if (props.Dirty)
            {
                string path = "";

                if (props.Path == null)
                {
                    if (saveFileDialog.ShowDialog() == DialogResult.OK)
                    {
                        path = saveFileDialog.FileName;
                    }
                    else
                    {
                        return;
                    }
                }
                else
                {
                    path = props.Path;
                }

                StreamWriter writer = new StreamWriter(path, false);
                writer.Write(txtInput.Text);
                writer.Close();
                (txtInput.Tag as ScriptFileProps).Dirty = false;

                Globals.ThisAddIn.ConsoleSetFocus();
            }
        }

        // Loads a PowerShell script into txtInput.Text

        private void OpenScriptFile()
        {
            if (openFileDialog.ShowDialog() == DialogResult.OK)
            {
                txtInput.Clear();

                StreamReader reader = new StreamReader(
                    openFileDialog.FileName);
                txtInput.Text = reader.ReadToEnd();
                reader.Close();

                txtInput.Tag = new ScriptFileProps(
                    false, openFileDialog.FileName);
            }

            Globals.ThisAddIn.ConsoleSetFocus();
        }

        // This is used to set txtOutput.Text
        // The operation needs to be asynchronous
        // because pipeline and txtOutput are in defferent threads.

        public void SetText(string text, bool append)
        {
            if (txtOutput.InvokeRequired)
            {
                SetTextDelegate d = new SetTextDelegate(SetText);
                this.Invoke(d, new Object[] { text, append });
            }
            else
            {
                if (append)
                {
                    txtOutput.AppendText(text + Environment.NewLine);
                }
                else
                {
                    txtOutput.Text = text;
                }
            }
        }
    }
}
